<?php

namespace App\Models\Payment\Platform;

use App\Jobs\PaymentOrderPaid;
use App\Models\Common\Constant;
use App\Models\Payment\PaymentChannel;
use App\Models\Payment\PaymentOrder;
use App\Models\Payment\RefundOrder;
use App\Models\Payment\TradeType;
use Godruoyi\Snowflake\Snowflake;
use GuzzleHttp\Client;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

// 富友前置商户支付
class FuiouAggregatePay extends BasePlatform implements BasePlatformInterface
{
    protected $mchntCd; // 富友商户号
    protected $subAppID; // 子商户公众号id,微信交易为商户的appid
    protected $rsaPrivateKey; // 加密私钥
    protected $snowflake;
    protected $uri = '';
    protected $signKeys;

    protected $termId; // 终端号 默认88888888

    const RULE = [
        'gateway' => 'string|min:10',
        'mchnt_cd' => 'required|string|min:8',
        'sub_app_id' => 'required|string|min:8',
        'mchnt_key' => 'required|string|min:20',
        'order_prefix' => 'required|string|min:4',
    ];

    const NOTIFY_CODE_SUCCESS = '1';
    const NOTIFY_CODE_FAIL = '0';

    const VERSION = '1.0';
    const GATEWAY = 'https://aipay-cloud.fuioupay.com';

    public function __construct(PaymentChannel $paymentChannel)
    {
        $this->gateway = self::GATEWAY;

        $parameters = $paymentChannel->parameters ?: [];
        $this->mchntCd = Arr::get($parameters, 'mchnt_cd');
        $this->subAppID = Arr::get($parameters, 'sub_app_id');
        $this->mchntKey = Arr::get($parameters, 'mchnt_key');
        if ($gateway = \Arr::get($parameters, 'gateway')) {
            $this->gateway = $gateway;
        }

        $this->logger = Log::channel('payment-fuiou-aggregate');

        $this->snowflake = new Snowflake();
    }

    public function toPay(string $tradeType, PaymentOrder $paymentOrder)
    {
        if ($paymentOrder->paid_at || $paymentOrder->order_status === Constant::ORDER_STATUS_PAID) {
            throw new \Exception($paymentOrder->paid_code . '已完成支付的订单不可重复发起支付');
        }

        $this->name = '申请支付参数:' . $tradeType;
        $this->nonceStr = self::nonceStr(32);
        $timeNow = now();
        $this->uri = '/aggregatePay/wxPreCreate';

        $toPayBody = [
            'version' => self::VERSION,
            'mchnt_cd' => $this->mchntCd,
            'random_str' => $this->nonceStr,
            'order_amt' => $paymentOrder->amount,
            'mchnt_order_no' => $paymentOrder->paid_code,
            'txn_begin_ts' => (clone $timeNow)->format('YmdHis'),
            'goods_des' => $paymentOrder->order_title,
            'term_id' => '88888888',
            'term_ip' => request()->getClientIp(),
            'notify_url' => $this->notifyUrl($paymentOrder->pay_type, $paymentOrder->payment_channel_id),
            'trade_type' => '',
//            'openid' => '',
            'sub_openid' => $paymentOrder->payer_id,
            'sub_appid' => '',
            'reserved_expire_minute' => 10
        ];

        switch ($tradeType) {
            case TradeType::WECHAT_NATIVE:
            case TradeType::ALI_NATIVE:
                $this->uri = '/aggregatePay/preCreate';

                $toPayBody = [
                    'version' => self::VERSION,
                    'mchnt_cd' => $this->mchntCd,
                    'term_id' => '88888888',
                    'random_str' => $this->nonceStr,
                    'order_type' => 'WECHAT',
                    'goods_des' => $paymentOrder->order_title,
                    'mchnt_order_no' => $paymentOrder->paid_code,
                    'order_amt' => $paymentOrder->amount,
                    'term_ip' => request()->getClientIp(),
                    'txn_begin_ts' => (clone $timeNow)->format('YmdHis'),
                    'notify_url' => $this->notifyUrl($paymentOrder->pay_type, $paymentOrder->payment_channel_id),
                    'reserved_expire_minute' => 10
                ];

                if (TradeType::ALI_NATIVE === $tradeType) {
                    $toPayBody['order_type'] = 'ALIPAY';
                }

                break;
            case TradeType::ALI_JSAPI:
                $toPayBody['trade_type'] = 'FWC';
                break;
            case TradeType::WECHAT_JSAPI:
                $toPayBody['trade_type'] = 'JSAPI';
                $toPayBody['sub_appid'] = $this->subAppID;
                break;
            case TradeType::WECHAT_MINI:
                $toPayBody['trade_type'] = 'LETPAY';
                $toPayBody['sub_appid'] = $this->subAppID;
                break;
            default:
                throw new \Exception('不支持的支付方式');
        }

        $this->body = $toPayBody;

        switch ($tradeType) {
            case TradeType::WECHAT_NATIVE:
            case TradeType::ALI_NATIVE:
            $this->signKeys = ['mchnt_cd', 'order_type', 'order_amt', 'mchnt_order_no', 'txn_begin_ts', 'goods_des', 'term_id', 'term_ip', 'notify_url', 'random_str', 'version'];
                break;
            default:
                $this->signKeys = ['mchnt_cd', 'trade_type', 'order_amt', 'mchnt_order_no', 'txn_begin_ts', 'goods_des', 'term_id', 'term_ip', 'notify_url', 'random_str', 'version'];
                break;
        }
        $this->signature();

        $payResponse = $this->fire();

        $error = '';
        if (Arr::get($payResponse, 'return_code') === 'SUCCESS' && Arr::get($payResponse, 'result_code') === 'SUCCESS') {
        } else {
            $error = Arr::get($payResponse, 'return_msg') ?: '未知原因';
        }
        $paymentOrder->error = $error;
        $paymentOrder->save();

        return $payResponse;
    }

    public function toQuery(PaymentOrder $paymentOrder)
    {
        $this->name = '订单查询';
        $this->nonceStr = self::nonceStr(32);
        $this->uri = '/aggregatePay/commonQuery';

        $toPayBody = [
            'version' => self::VERSION,
            'mchnt_cd' => $this->mchntCd,
            'term_id' => '88888888',
            'mchnt_order_no' => $paymentOrder->paid_code,
            'random_str' => $this->nonceStr,
            'sign' => '',
        ];

        switch ($paymentOrder->trade_type) {
            case TradeType::ALI_JSAPI:
            case TradeType::ALI_NATIVE:
                $toPayBody['order_type'] = 'ALIPAY';
                break;
            case TradeType::WECHAT_MINI:
            case TradeType::WECHAT_JSAPI:
            case TradeType::WECHAT_NATIVE:
            default:
                $toPayBody['order_type'] = 'WECHAT';
                break;
        }

        $this->body = $toPayBody;
        $this->signKeys = ['mchnt_cd', 'order_type', 'mchnt_order_no', 'term_id', 'random_str', 'version'];
        $this->signature();

        return $this->fire();
    }

    public function toRefund(RefundOrder $refundOrder)
    {
        $this->name = '订单退款';
        $this->nonceStr = self::nonceStr(32);
        $this->uri = '/aggregatePay/commonRefund';

        $toPayBody = [
            'version' => self::VERSION,
            'mchnt_cd' => $this->mchntCd,
            'term_id' => '88888888',
            'mchnt_order_no' => $refundOrder->paid_code,
            'random_str' => $this->nonceStr,
            'refund_order_no' => $refundOrder->refund_code,
            'total_amt' => $refundOrder->refund_amount,
            'refund_amt' => $refundOrder->refund_amount,
            'operator_id' => '',
        ];

        $paymentOrder = PaymentOrder::where('paid_code', $refundOrder->paid_code)->first();
        switch (optional($paymentOrder)->trade_type) {
            case TradeType::ALI_JSAPI:
            case TradeType::ALI_NATIVE:
                $toPayBody['order_type'] = 'ALIPAY';
                break;
            case TradeType::WECHAT_MINI:
            case TradeType::WECHAT_JSAPI:
            case TradeType::WECHAT_NATIVE:
            default:
                $toPayBody['order_type'] = 'WECHAT';
                break;
        }

        $this->body = $toPayBody;
        $this->signKeys = ['mchnt_cd', 'order_type', 'mchnt_order_no', 'refund_order_no', 'total_amt', 'refund_amt', 'term_id', 'random_str', 'version'];
        $this->signature();

        return $this->fire();
    }

    public function toClose(PaymentOrder $paymentOrder)
    {
        $this->name = '订单关闭';
        $this->nonceStr = self::nonceStr(32);
        $this->uri = '/aggregatePay/closeOrder';

        $toPayBody = [
            'version' => self::VERSION,
            'mchnt_cd' => $this->mchntCd,
            'term_id' => '88888888',
            'mchnt_order_no' => $paymentOrder->paid_code,
            'random_str' => $this->nonceStr,
            'sign' => '',
            'sub_appid' => $this->subAppID,
            'operator_id' => '',
        ];

        switch ($paymentOrder->trade_type) {
            case TradeType::ALI_NATIVE:
            case TradeType::ALI_JSAPI:
                $toPayBody['order_type'] = 'ALIPAY';
                break;
            case TradeType::WECHAT_MINI:
            case TradeType::WECHAT_JSAPI:
            case TradeType::WECHAT_NATIVE:
            default:
                $toPayBody['order_type'] = 'WECHAT';
                break;
        }

        $this->body = $toPayBody;
        $this->signKeys = ['mchnt_cd', 'mchnt_order_no', 'order_type', 'term_id', 'random_str', 'version'];
        $this->signature();

        return $this->fire();
    }

    protected static function nonceStr($len = 16): string
    {
        return Str::random($len);
    }

    protected static function timestamp(): string
    {
        return '';
    }

    protected function signature(): string
    {
        $signValues = [];

        $signKeys = array_unique($this->signKeys);
        foreach ($signKeys as $key) {
            if ($value = Arr::get($this->body, $key)) {
                $signValues[] = $value;
            } else {
                throw new \Exception('字段'.$key.'为空');
            }
        }
        $signValues[] = $this->mchntKey;

        $signature = $this->mkSignature($signValues);
        $this->body['sign'] = $signature;
        return $signature;
    }

    private function mkSignature(array $queryBody): string
    {
        $stringToSign = implode('|', $queryBody);

        // Ensure the string is encoded in UTF-8 before hashing.
        $utf8StringToSign = mb_convert_encoding($stringToSign, 'UTF-8');

        // Calculate the MD5 hash of the string.
        $signature = md5($utf8StringToSign);

        return $signature;
    }

    protected function fire()
    {
        $httpClient = new Client(['base_uri' => $this->gateway, 'timeout' => self::TIMEOUT, 'verify' => false, 'headers' => ['Content-Type' => 'application/json;charset=utf-8']]);
        $httpMethod = strtoupper($this->method);
        $respBody = [];

        $logPacket = ['gateway' => $this->gateway, 'queryBody' => $this->body];

        try {
            $rawResponse = $httpClient->request($httpMethod, $this->uri, ['json' => $this->body]);
            $respBody = optional(optional($rawResponse)->getBody())->getContents() ?: '';
            if (is_string($respBody)) {
                $respBody = json_decode($respBody, true);
            }
            $logPacket['response'] = $respBody;
        } catch (\Throwable $exception) {
            $logPacket['err'] = $exception->getMessage();
        } finally {

            $this->logger->info($this->name, $logPacket);

            if (isset($exception)) {
                throw $exception;
            }
        }

        $this->responseAdapter($respBody);

        return $respBody;
    }

    protected function notifySignCheck($rawNotifyDat): bool
    {
        if (!$notifySign = Arr::get($rawNotifyDat, 'sign')) {
            return false;
        }

        $this->body = $rawNotifyDat;
        $this->signKeys = ['mchnt_cd', 'mchnt_order_no', 'settle_order_amt', 'order_amt', 'txn_fin_ts', 'reserved_fy_settle_dt', 'random_str'];

        return $notifySign === $this->signature();
    }

    static public function optionalDatValidator(string $tradeType, array $optionalDat): array
    {
        return [];
    }

    public static function notifyDatFilter($rawNotifyDat): array
    {
        return [];
    }

    public function handleNotify(PaymentOrder $paymentOrder, array $notifyDat)
    {
        $paymentSuccessful = false;
        $logName = $paymentOrder->pay_type . ':' . $paymentOrder->payment_channel_id;

        if (!$this->notifySignCheck($notifyDat)) {
            Log::channel('payment-notify-error')->info($logName, ['err' => '支付回调验签失败', 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
            return self::NOTIFY_CODE_FAIL;
        }

        if (!$outTransactionID = Arr::get($notifyDat, 'transaction_id')) {
            Log::channel('payment-notify-error')->info($logName, ['err' => '支付回调缺失重要参数流水单号', 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
            return self::NOTIFY_CODE_FAIL;
        }

        if ($paymentOrder->order_status === Constant::ORDER_STATUS_PAID) {
            if ($paymentOrder->out_trade_no && ($paymentOrder->out_trade_no !== $outTransactionID)) {
                Log::channel('payment-notify-error')->info($logName, ['err' => '订单重复支付(out_transaction_id)', 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
                return self::NOTIFY_CODE_SUCCESS;
            } else {
                Log::channel('payment-notify-error')->info($logName, ['err' => '订单已支付', 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
            }
            return self::NOTIFY_CODE_SUCCESS;
        }

        $paidAmount = intval(Arr::get($notifyDat, 'order_amt'));

        if ($paidAmount !== $paymentOrder->amount) {
            Log::channel('payment-notify-error')->info($logName, ['err' => "回调通知的支付金额与订单的支付金额不一致({$paidAmount}/{$paymentOrder->amount})", 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
            return self::NOTIFY_CODE_FAIL;
        }

        try {

            if (Arr::get($notifyDat, 'result_code') === '000000') {

                $paymentOrder->paid_at = now()->toDateTimeString();
                $paymentOrder->out_trade_no = $outTransactionID;
                $paymentOrder->frozen_balance_amount = 0;
                $paymentOrder->frozen_point = 0;
                $paymentOrder->order_status = Constant::ORDER_STATUS_PAID;

                $paymentOrder->save();
                $paymentSuccessful = true;
            }
        } catch (\Throwable $exception) {
            Log::channel('payment-notify-error')->info($logName, ['err' => $exception->getMessage(), 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
        } finally {
            if ($paymentSuccessful) {
                //处理后续的各类系统订单逻辑
                PaymentOrderPaid::dispatch($paymentOrder)->onQueue(Constant::QUEUE_PAYMENT);

                return self::NOTIFY_CODE_SUCCESS;
            }
        }
        return self::NOTIFY_CODE_FAIL;
    }

    public function responseAdapter(array &$response = []): array
    {
        $resultCode = Arr::get($response, 'result_code');

        if ($resultCode === '000000') {
            $response['result_code'] = $response['return_code'] = 'SUCCESS';
            $response['return_msg'] = 'OK';

            $response['trade_state_desc'] = $response['trade_state'] = strval(Arr::get($response, 'result_code'));

            if ($qrCode = Arr::get($response, 'qr_code')) {
                $response['pay_info'] = [
                    'qr_code' => $qrCode
                ];
            }
            else {
                $response['pay_info'] = [
                    'appId' => Arr::get($response, 'sdk_appid'),
                    'timeStamp' => Arr::get($response, 'sdk_timestamp'),
                    'nonceStr' => Arr::get($response, 'sdk_noncestr'),
                    'package' => Arr::get($response, 'sdk_package'),
                    'signType' => Arr::get($response, 'sdk_signtype'),
                    'paySign' => Arr::get($response, 'sdk_paysign'),
                    'partnerid' => Arr::get($response, 'sdk_partnerid'),
                ];
            }
        } else {
            $errCode = Arr::get($response, 'result_code');
            $errMsg = Arr::get($response, 'result_msg') ?: 'ERROR';
            $response['result_code'] = $response['return_code'] = 'FAIL';
            $response['return_msg'] = $response['trade_state_desc'] = $errMsg;
            $response['trade_state'] = $errCode;
        }

        unset($response['sdk_appid']);
        unset($response['sdk_timestamp']);
        unset($response['sdk_noncestr']);
        unset($response['sdk_package']);
        unset($response['sdk_signtype']);
        unset($response['sdk_paysign']);
        unset($response['sdk_partnerid']);

        return $response;
    }

    public function paidCode(): string
    {
        return $this->snowflake->id();
    }

    public function arr2xml($arrDat): string
    {
        /*
         * <?xml version="1.0" encoding="GBK" standalone="yes"?>
         */
        $xml = '<?xml version="1.0" encoding="GBK" standalone="yes"?>'.PHP_EOL.'<xml>'.PHP_EOL;
        foreach ($arrDat as $key => $val) {
            $xml .= '<' . $key . '>' . $val . '</' . $key . '>'.PHP_EOL;
        }
        $xml .= '</xml>';

        return $xml;
    }
}
